﻿/*----------------------------------------------------------------
// 公司名称：公司名称
// 
// 产品名称：Gaf
//
// 创 建 人：netcasewqs
// 创建日期：2022/3/4
// 邮    箱：netcasewqs@qq.com
//
// Copyright (C) 2022 Gaf，保留所有权利。
//
//----------------------------------------------------------------*/
using System.Reflection.Metadata;

namespace Gaf.Impl;


record ActivatorDescriptor
{
    public ConstructorInvoker Constructor { get; protected set; } = null!;

    public MethodInvoker? Start { get; protected set; }
    public IConfigurationMethodInvoker? Configure { get; protected set; }
    public MethodInvoker? Match { get; protected set; }
    public MethodInvoker? Stop { get; protected set; }

    public MethodInfo? SetAssembliesMethod { get; protected set; }

    protected static readonly Type _voidType = typeof(void);
    protected const string set_Assemblies = nameof(set_Assemblies);
    protected const string _startMethodName = nameof(IAddinActivator.Start);
    protected const string _configureMethodName = nameof(IAddinActivator.Configure);
    protected const string _matchMethodName = nameof(IAddinActivator.Match);
    protected const string _stopMethodName = nameof(IAddinActivator.Stop);


    internal static bool IsManifestParameter(ParameterInfo p)
    {
        return p.ParameterType == typeof(IConfiguration);
    }

    internal static bool IsAssembliesParameter(ParameterInfo p)
    {
        return p.ParameterType == typeof(IEnumerable<Assembly>);
    }



    private static ConstructorInvoker? GetConstructor(Type t)
    {
        var items = from ctor in t.GetConstructors()
                    let ps = ctor.GetParameters()
                    where ps.Length <= 2
                    orderby ps.Length descending
                    select (ctor, ps);

        foreach (var (ctor, ps) in items)
        {
            if (ps.Length is 2)
            {
                ParameterInfo? assemblies = null;
                ParameterInfo? manifest = null;

                var p = ps[0];
                if (IsAssembliesParameter(p))
                {
                    assemblies = p;
                }
                else if (IsManifestParameter(p))
                {
                    manifest = p;
                }
                else
                {
                    continue;
                }

                p = ps[1];
                if (IsAssembliesParameter(p))
                {
                    if (assemblies != null) continue;
                    assemblies = p;
                }
                else if (IsManifestParameter(p))
                {
                    if (manifest != null) continue;
                    manifest = p;
                }
                else
                {
                    continue;
                }

                if (assemblies != null && manifest != null)
                    return new ConstructorInvoker(ctor, assemblies, manifest);
            }
            else if (ps.Length == 1)
            {
                var p = ps[0];
                if (IsAssembliesParameter(p))
                {
                    return new ConstructorInvoker(ctor, p, null);
                }
                else if (IsManifestParameter(p))
                {
                    return new ConstructorInvoker(ctor, null, p);
                }
            }
            else if (ps.Length == 0)
            {
                return new ConstructorInvoker(ctor, null, null);
            }
        }

        return default;
    }

    private static MethodInvoker? GetStartOrStopMethod(MethodInfo[] methods,bool startMethod)
    {
        var methodName = startMethod ? _startMethodName : _stopMethodName;
        var m = methods.FirstOrDefault(method => method.Name == methodName && method.ReturnType == _voidType);
        if (m == null) return null;
        var ps = m.GetParameters();

        if (ps.Length == 0) return new MethodInvoker(m);
        else if (ps.Length == 1 && ps[0].ParameterType == typeof(IServiceProvider))
        {
            return new MethodInvoker(m, ps[0]);
        }
        else
        {
            var tmpPs = ps.Where(p => !typeof(IServiceProvider).IsAssignableFrom(p.ParameterType)).ToArray();
            if (tmpPs.All(parameter => !IsPrimitiveType(parameter.ParameterType)))
                return new MultiParametersMethodInvoker(m, ps);
        }

        return null;

    }

    internal static bool IsPrimitiveType(Type type)
    {
        return type.IsPrimitive //
                              || typeof(string) == type
                              || typeof(char[]) == type
                              || typeof(byte[]) == type
                              || typeof(Decimal) == type
                              || typeof(Guid) == type
                              || typeof(DateTime) == type
                              || typeof(DateTimeOffset) == type
#if NET6_0_OR_GREATER
                              || typeof(DateOnly) == type
                              || typeof(TimeOnly) == type
#endif
                              || typeof(TimeSpan) == type
                              || type.IsEnum
                              || type.Name.AsSpan().StartsWith($"{nameof(Tuple)}<")
                              || Nullable.GetUnderlyingType(type) != null;
    }

    private static IConfigurationMethodInvoker? GetConfigureMethod(Type activatorType, MethodInfo[] methods)
    {
        var ms = (from m in methods.Where(method => method.Name == _configureMethodName && method.ReturnType == _voidType)
                  let ps = m.GetParameters()
                  where ps.All(p => !IsPrimitiveType(p.ParameterType))
                  select (m, ps)).ToList();

        if (ms.Count == 0) return default;

        //if (ms.Count > 1)
        //{
        //    throw new InvalidProgramException($"{ms[0].m.DeclaringType!.FullName}.{_configureMethodName} 重复定义!");
        //}

        var (method, parameters) = ms.FirstOrDefault(x=>x.m.DeclaringType == activatorType); //优先取当前类定义的Configure 方法，没有则取基类定义的方法
        if (method == null)
            (method, parameters) = ms[0];

        return new ConfigurationMethodInvoker(method, parameters);
    }

    private static MethodInvoker? GetMatchMethod(MethodInfo[] methods)
    {
        var m = methods.FirstOrDefault(method => method.Name == _matchMethodName && method.ReturnType == typeof(bool));
        if (m == null) return null;
        var ps = m.GetParameters();

        if (ps.Length == 0) return new MethodInvoker(m);
        else if (ps.Length == 1 && ps[0].ParameterType == typeof(IServiceProvider))
        {
            return new MethodInvoker(m, ps[0]);
        }
        else
        {
            var tmpPs = ps.Where(p => !typeof(IServiceProvider).IsAssignableFrom(p.ParameterType)).ToArray();
            if (tmpPs.All(parameter => !IsPrimitiveType(parameter.ParameterType)))
                return new MultiParametersMethodInvoker(m, ps);
        }

        return null;

    }
    public static ActivatorDescriptor? ParseActivatorDescriptor(Type t)
    {
        //判读是否以Activator 结尾
        if (!t.Name.EndsWith("Activator")) return null;

        ConstructorInvoker? constructor = GetConstructor(t);

        if (constructor == null) return null;

        var methods = t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance| BindingFlags.Static);

        //start method
        var startMethod = GetStartOrStopMethod(methods,startMethod:true);
        if (startMethod == null) return null;

        var result = new ActivatorDescriptor { Constructor = constructor };
        result.Start = startMethod;
        result.Configure = GetConfigureMethod(t,methods);
        result.Match = GetMatchMethod(methods);
        result.Stop = GetStartOrStopMethod(methods, startMethod: false); 


        if (constructor.Assemblies == null)
        {
            result.SetAssembliesMethod = methods.FirstOrDefault(method => method.Name == set_Assemblies
             && method.ReturnType == _voidType
             && method.GetParameters().Length == 1
             && method.GetParameters()[0].ParameterType == typeof(IEnumerable<Assembly>));
        }


        return result;
    }
}

